﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Speech.Recognition;
using CodeTalk.CTSpeechCommands;

namespace CodeTalk
{
    public class SpeechMode
    {
        internal Context CurrentContext { get; private set; }
        public OutputHandler CurrentOutputHandler { get; private set; }
        public GUIHandler CurrentGuiHandler { get; private set; }
        public string CurrentSolution { get; private set; }
        public string CurrentWorkFile { get; private set; }

        protected Grammar grammar;
        protected SpeechRecognitionEngine Engine;

        public string Name { get; private set; }
        public string FilePath { get; private set; }

        //protected List<SpeechCommand> AllSpeechCommands;
        private Dictionary<string, SpeechCommand> AllSpeechCommands;

        private List<SpeechCommand> speechCommands;
        public List<SpeechCommand> SpeechCommands
        {
            get { return speechCommands; }
        }

        private List<TextCommand> textCommands;
        public List<TextCommand> TextCommands
        {
            get { return textCommands; }
        }

        private List<Context> librariesUsed;

        public List<Context> LibrariesUsed
        {
            get { return librariesUsed; }
        }

        private SpeechCommand GetSpeechCommand(SemanticValue sem)
        {
            string SemanticKey = (string)sem["SemanticKey"].Value;
            if (AllSpeechCommands.ContainsKey(SemanticKey))
                return AllSpeechCommands[SemanticKey];
            else
                return null;
        }

        public void OnSpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            try
            {
                SpeechCommand sc = GetSpeechCommand(e.Result.Semantics);

                if (sc != null)
                {
                    ActionManager.ExecuteCommand(sc, this, e.Result.Text, e.Result.Semantics);
                }
                else
                {
                    System.Windows.Forms.MessageBox.Show("OnSpeechRecognized ERROR!");
                    return;
                }

                ActionManager.ClearAlternativeActions();
                List<string> alternativesFeedback = new List<string>();
                bool firstOneSkipped = false;
                foreach (var alt in e.Result.Alternates)
                {
                    if (firstOneSkipped)
                    {
                        sc = GetSpeechCommand(alt.Semantics);

                        if (sc != null)
                        {
                            alternativesFeedback.Add(sc.GetAlternativesFeedback(alt.Text, alt.Semantics));
                            ActionManager.AddAlternativeAction(sc, this, alt.Text, alt.Semantics);
                        }
                    }
                    firstOneSkipped = true;
                }
                CurrentGuiHandler.SetAlternativesFeedback(alternativesFeedback.ToArray());
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("Internal error in OnSpeechRecognized: " + ex);
                return;
            }
        }

        public SpeechMode(string FilePath, OutputHandler outputHandler, GUIHandler guiHandler, Context context)
        {
            CurrentContext = context;
            CurrentOutputHandler = outputHandler;
            CurrentGuiHandler = guiHandler;
            CurrentSolution = "";
            CurrentWorkFile = "";
            Name = System.IO.Path.GetFileNameWithoutExtension(FilePath);
            this.FilePath = FilePath;

            textCommands = new List<TextCommand>();
            speechCommands = new List<SpeechCommand>();
            AllSpeechCommands = new Dictionary<string, SpeechCommand>();

            librariesUsed = new List<Context>();
            
            CreateAllSpeechCommands();
        }

        public virtual bool Init(SpeechRecognitionEngine engine)
        {
            Engine = engine;
            BuildGrammar();
            return grammar != null;
        }

        protected void BuildGrammar()
        {
            Context[] libraries = librariesUsed.ToArray();
            Choices ch = new Choices();
            int nChoices = 0;
            foreach (var sc in speechCommands)
            {
                if (sc.Active)
                {
                    GrammarBuilder gb = sc.GrammarBld(CurrentContext, libraries);
                    if (gb != null)
                    {
                        gb.Append(new SemanticResultKey("SemanticKey", new SemanticResultValue(sc.SemanticKey)));
                        ch.Add(gb);
                        nChoices++;
                    }
                }
            }
            foreach (var tc in textCommands)
            {
                if (tc.Active)
                {
                    GrammarBuilder gb = tc.GrammarBld(CurrentContext, libraries);
                    if (gb != null)
                    {
                        gb.Append(new SemanticResultKey("SemanticKey", new SemanticResultValue(tc.SemanticKey)));
                        ch.Add(gb);
                        nChoices++;
                    }
                }
            }
            if (nChoices != 0)
                grammar = new Grammar(ch);
            else
                grammar = null;
        }

        private void CreateAllSpeechCommands()
        {
            SpeechCommands.Clear();
            SpeechCommands.Add(new InsertTypeCommand());
            SpeechCommands.Add(new CSInsertArray());
            SpeechCommands.Add(new CSInsertGeneric());
            SpeechCommands.Add(new InsertOpen());
            SpeechCommands.Add(new InsertFunction());
            SpeechCommands.Add(new InsertLocal());
            SpeechCommands.Add(new InsertHidden());
            SpeechCommands.Add(new InsertVariable());

            SpeechCommands.Add(new LearnVariable());
            SpeechCommands.Add(new LearnFunction());
            SpeechCommands.Add(new LearnType());

            SpeechCommands.Add(new NewSymbol());
            SpeechCommands.Add(new PressKey());
            SpeechCommands.Add(new CodeBlockCommand());
            SpeechCommands.Add(new DictationCommand());
            SpeechCommands.Add(new UndoRedo());
            SpeechCommands.Add(new CorrectionCommand());
            SpeechCommands.Add(new MoveCommand());

            AllSpeechCommands.Clear();
            foreach (var sc in SpeechCommands)
                AllSpeechCommands.Add(sc.SemanticKey, sc);

            if (TextCommands != null && TextCommands.Count > 0)
                foreach (var tc in TextCommands)
                    AllSpeechCommands.Add(tc.SemanticKey, tc);
        }

        public virtual void RecompileGrammar()
        {
            CreateAllSpeechCommands();
            if (Engine != null && grammar != null && grammar.Enabled)
            {
                grammar.Enabled = false;
                Engine.UnloadAllGrammars();
                BuildGrammar();
                grammar.Enabled = true;
                Engine.LoadGrammar(grammar);
            }
            else
            {
                BuildGrammar();
            }
        }

        public virtual void Activate()
        {
            if (grammar != null)
            {
                grammar.Enabled = true;
                Engine.UnloadAllGrammars();
                Engine.LoadGrammar(grammar);
            }
        }

        public virtual void Deactivate()
        {
            if (grammar != null) grammar.Enabled = false;
        }

        public void SetContext(Context context)
        {
            CurrentContext = context;
            RecompileGrammar();
        }

        public bool CreateNewTextCommand(string writtenForm, string spokenForm, ref string errorMessage)
        {
            spokenForm = spokenForm.Trim();
            foreach (var sc in AllSpeechCommands)
            {
                string lcSpokenForm = spokenForm.ToLowerInvariant();
                if (sc.Value.CanSpokenFormMatch(lcSpokenForm))
                {
                    errorMessage = "New Text command collides with existing Speech command '" + sc.Value.Name + "'.";
                    return false;
                }
            }

            try
            {
                TextCommand newTextCommand = new TextCommand(writtenForm, spokenForm);
                textCommands.Add(newTextCommand);
                RecompileGrammar();
                return true;
            }
            catch (Exception ex)
            {
                errorMessage = "Error creating new Text command: " + ex;
                return false;
            }
        }

        public bool DeleteTextCommandWithSpokenForm(string spokenForm)
        {
            for (int i = 0; i < TextCommands.Count; i++)
            {
                if (TextCommands[i].SpokenForm == spokenForm)
                {
                    TextCommands.RemoveAt(i);
                    return true;
                }
            }
            return false;
        }

        public bool AddLibraryUsed(Context library)
        {
            foreach (var lib in librariesUsed)
            {
                if (library.Name == lib.Name)
                    return false;
            }

            librariesUsed.Add(library);
            RecompileGrammar();
            return true;
        }

        public bool RemoveLibraryUsed(string name)
        {
            for (int i = 0; i < librariesUsed.Count; i++)
            {
                if (librariesUsed[i].Name == name)
                {
                    librariesUsed.RemoveAt(i);
                    RecompileGrammar();
                    return true;
                }
            }
            return false;
        }

        public bool IsLibraryUsed(string name)
        {
            for (int i = 0; i < librariesUsed.Count; i++)
            {
                if (librariesUsed[i].Name == name)
                    return true;
            }
            return false;
        }

        public void ClearLibrariesUsed()
        {
            librariesUsed.Clear();
            RecompileGrammar();
        }

        public CodeSymbol GetSymbolWithIDFromAnyLibraryUsed(uint id)
        {
            CodeSymbol cs = CurrentContext.GetSymbolWithID(id);
                if (cs != null)
                    return cs;

            foreach (var lib in LibrariesUsed)
            {
                cs = lib.GetSymbolWithID(id);
                if (cs != null)
                    return cs;
            }
            return null;
        }

        public bool Save()
        {
            return SaveToFile(FilePath);
        }

        public bool SaveToFile(string filePath)
        {
            try
            {
                Name = System.IO.Path.GetFileNameWithoutExtension(filePath);
                using (StreamWriter sw = new StreamWriter(filePath, false))
                {
                    sw.WriteLine("SpeechMode: " + Name);
                    sw.WriteLine("TextCommands: " + textCommands.Count);
                    foreach (var tc in textCommands)
                        sw.WriteLine(tc.Serialize());
                    sw.WriteLine("");
                    sw.WriteLine("SpeechCommands: " + SpeechCommands.Count);
                    foreach (var sc in SpeechCommands)
                    {
                        sw.WriteLine(sc.Name + ": " + sc.Active);
                    }
                }
                FilePath = filePath;
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public static SpeechMode LoadFromFile(string filePath, OutputHandler outputHandler, GUIHandler guiHandler, Context context)
        {
            try
            {
                SpeechMode newSpeechMode = null;
                using (StreamReader sr = new StreamReader(filePath))
                {
                    string line = sr.ReadLine();
                    if (!line.StartsWith("SpeechMode: "))
                        return null;
                    newSpeechMode = new SpeechMode(filePath, outputHandler, guiHandler, context);

                    line = sr.ReadLine();
                    if (!line.StartsWith("TextCommands: "))
                        return null;
                    line = line.Substring(14);
                    int nTextCommands = int.Parse(line);

                    for (int i = 0; i < nTextCommands; i++)
                    {
                        line = sr.ReadLine();
                        TextCommand tc = TextCommand.Deserialize(line);
                        if (tc != null && tc.SpokenForm.Trim().Length > 0 && tc.WrittenForm.Length > 0)
                            newSpeechMode.textCommands.Add(tc);
                    }

                    line = sr.ReadLine();
                    line = sr.ReadLine();
                    if (!line.StartsWith("SpeechCommands: "))
                        return null;
                    line = line.Substring(16);
                    int nSpeechCommands = int.Parse(line);

                    for (int i = 0; i < nSpeechCommands; i++)
                    {
                        line = sr.ReadLine();
                        string name = line.Substring(0, line.LastIndexOf(':'));
                        foreach (var sc in newSpeechMode.SpeechCommands)
                            if (sc.Name == name)
                                sc.Active = bool.Parse(line.Substring(name.Length + 2));
                    }

                    newSpeechMode.CreateAllSpeechCommands();
                }
                return newSpeechMode;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}
